/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.editor;

import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.eclipse.draw2d.geometry.Point;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Model;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.Profile;

import com.cea.papyrus.core.IPapyrusUIConstants;
import com.cea.papyrus.core.presentation.UMLDecoratedLabelProvider;
import com.cea.papyrus.core.presentation.sorters.TypeSorter;
import com.cea.papyrus.core.treeeditpart.ElementTreeEditPart;
import com.cea.papyrus.core.utils.DiResourceSet;
import com.cea.papyrus.core.utils.FilteredCollectionViewFromIterator;
import com.cea.papyrus.core.utils.IFilter;
import com.cea.papyrus.diagraminterchange2.di2.Di2Factory;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;
import com.cea.papyrus.diagraminterchange2.di2.impl.DiagramImpl;
import com.cea.papyrus.diagraminterchange2.di2.util.Di2Switch;

/**
 * Model Manager.
 * This manager provides useful methods.
 * This manager should know nothing about the editor.
 * 
 * @author dumoulin
 */
public class ModelManager {

	/** Resource Set for all EMF elements required by this editor */
	protected DiResourceSet diResourceSet;

	/**
	 * 
	 */
	private ElementTreeEditPart modelTreeEditPart = null;

	/**
	 * Listener on diagram added/removed.
	 */
	private PropertyChangeSupport ModelManagerListener = new PropertyChangeSupport(this);

	
	/**
	 * Darpan,
	 */
	public static final String DIAGRAM_ABOUT_TO_ADD = "About to add diagram";
	
	/**
	 * 
	 */
	public static final String CHILD_ADDED_PROP = "ChildAdded";

	/**
	 * 
	 */
	public static final String UMLCHILD_MODIFIED_PROP = "ChildModify";

	/**
	 * 
	 */
	public static final String CHILD_REMOVED_PROP = "ChildRemoved";

	/**
	 * 
	 */
	public static final String UMLCHILD_REMOVED_PROP = "UMLChildRemoved";

	/**
	 * 
	 */
	public static final String UMLCHILD_ADDED_PROP = "UMLChildAdded";

	/**
	 * 
	 */
	public static final String CHILD_MODIFIED_PROP = "ChildModifiedAdded";

	/** The filter used to get Diagram elements */
	protected static IFilter diagramFilter = new DiagramFilter();

	/** The filter used to get Diagram elements */
	protected static IFilter visibleDiagramFilter = new VisibleDiagramFilter();

	/** Provider used to get the collection of elements from the diResource */
	protected DiAllContentsProvider diAllContentsProvider = new DiAllContentsProvider();

	protected final UMLDecoratedLabelProvider adapterFactoryLabelProvider;

	/** Comparator for the outline */
	private Comparator sorter;

	/**
	 * Returns the EMF Resource of the UML model.
	 * @return the EMF resource of the UML model
	 */
	public Resource getModelResource() {
		return diResourceSet.getUMLModelResource();		
	}

	/**
	 * Returns the model manager listener associated to this editor
	 * @return the model manager listener associated to this editor
	 */
	public PropertyChangeSupport getModelManagerListener(){
		return ModelManagerListener;
	}

	/**
	 * Get the EMF Resource of the DI.
	 * @return the emf resource for the DI
	 */
	public Resource getDiResource() {
		return diResourceSet.getDiResource();
	}

	/**
	 * get the root element (of type Model or Profile) of the uml model. 
	 * The root element can be a:<ul> 
	 * <li>Model</li>
	 * <li>Package</li>
	 * <li>Profile</li>
	 * </ul>
	 * @return the top element for the edited model 
	 */
	public Package getTopPackageFromUMLResource() {
		return getDiResourceSet().getTopPackageFromUMLResource();
		/*
		Package model = null;
		if (getModelResource().getContents().size() < 1) {
			return null;
		}
		if (getModelResource().getContents().get(0) instanceof Package) {
			model = (Package) getModelResource().getContents().get(0);
		}

		return model;
		 */
	}

	/**
	 * Add the specified diagram to model.
	 * @param diagram the diagram to add
	 */
	public boolean addDiagram(Diagram diagram) {
		// Add it to resource, so that it will be saved.
		boolean add = getDiResource().getContents().add(diagram);

		// fire change 
		ModelManagerListener.firePropertyChange(CHILD_ADDED_PROP, null, diagram);
		return add;
	}

	/**
	 * Creates a new ModelManager, and initialize its associated DiResourceSet.
	 */
	public ModelManager(AdapterFactory adapterFactory) {
		this.diResourceSet = new DiResourceSet(adapterFactory);
		adapterFactoryLabelProvider = new UMLDecoratedLabelProvider(adapterFactory);
		sorter = new TypeSorter();
	}

	/**
	 * Add the specified diagram to model.
	 * 
	 * @param impl 	 * @param gp 
	 */
	public void modifGraphElement(GraphElement gp){
		ModelManagerListener.firePropertyChange(CHILD_MODIFIED_PROP, null, gp);
	}


	/**
	 * Return all GraphElements that are linked to the element.
	 * 
	 * @param element for which we look for all graphElement
	 * @param found contained of result
	 * 
	 * @return list of all graphElements
	 */
	public ArrayList<GraphElement> lookForGraphElement(Element element,ArrayList<GraphElement> found){
		Iterator itDiagram=getDiagrams().iterator();
		//for each diagrams
		while (itDiagram.hasNext()){
			Diagram currentDiagram =(Diagram)itDiagram.next();
			found=lookForGraphElement(element, currentDiagram, found);
		}
		return found;
	}


	/**
	 * Return all GraphNode that are linked tohe element at specific level graphElement.
	 * 
	 * @param researched element for which we look for all graphElement
	 * @param currentNode 
	 * @param found ontained of result
	 * 
	 * @return list of all graphElements
	 */
	private ArrayList<GraphElement> lookForGraphElement(Element researched,GraphElement currentNode, ArrayList<GraphElement> found ){
		Element lookE = null;
		if (!(currentNode instanceof DiagramImpl)){
			lookE=null;

			//test the case where graphNode is not associated to a uml elment : 
			//example Container of feature
			if (((Uml1SemanticModelBridge)(currentNode.getSemanticModel()))!=null){
				lookE=((Uml1SemanticModelBridge)(currentNode.getSemanticModel())).getElement();
			}
		}
		else { 
			if(((Diagram)currentNode).getOwner()!=null) {
				lookE=((Uml1SemanticModelBridge)(((Diagram)currentNode).getOwner())).getElement();
			}
		}

		if (lookE!=null){
			//if((lookE.getQualifiedName()).equals(researched.getQualifiedName())){
			if(lookE.equals(researched)){
				found.add(currentNode);
			}
		}
		Iterator itNode=currentNode.getContained().iterator();
		while (itNode.hasNext()){
			GraphElement subNode =(GraphElement)itNode.next();
			found=lookForGraphElement(researched, subNode, found);
		}
		return found;
	}

	/**
	 * Add the specified diagram to model.
	 * 
	 * @param element 
	 * @param impl 	 */
	public void modifyUMLElement(Element element) {
		// fire change 
		ModelManagerListener.firePropertyChange(UMLCHILD_MODIFIED_PROP, null, element);
	}

	/**
	 * Create a new class diagram at the root of the uml model.
	 * 
	 * @param name 
	 * 
	 * @return 
	 */
	public Diagram createClassDiagram( String name )
	{
		return createDiagram(getTopPackageFromUMLResource(), name, IPapyrusUIConstants.CLASS_DIAGRAM);
	}

	/**
	 * Create a new use case diagram at the root of the uml model.
	 * 
	 * @param name 
	 * 
	 * @return 
	 */
	public Diagram createUseCaseDiagram( String name )
	{
		return createDiagram(getTopPackageFromUMLResource(), name, IPapyrusUIConstants.USE_CASE_DIAGRAM);
	}

	/**
	 * Create a new class diagram.
	 * 
	 * @param umlOwner 
	 * @param type 
	 * @param owner The future parent of the created diagram.
	 * @param name 
	 * 
	 * @return 
	 */
	@SuppressWarnings("unchecked")
	public Diagram createDiagram(Element umlOwner, String name, String type )
	{
		// create diagram
		Diagram diagram = Di2Factory.eINSTANCE.createDiagram();
		diagram.setIsVisible(true);
		diagram.setType(type);
		// create associated node
		Uml1SemanticModelBridge semantic= Di2Factory.eINSTANCE.createUml1SemanticModelBridge();
		semantic.setElement(umlOwner);


		diagram.setOwner(semantic);
		diagram.setPosition(new Point(0,0));
		diagram.setFontFamily("Arial");
		diagram.setBackgroundColor(new RGB(255,255,255));//white
		diagram.setBorderColor(new RGB(255,255,255));//white
		diagram.setFontColor(new RGB(255,255,255));//white
		diagram.setFontSize(10);
		diagram.setLineStyle("solid");
		diagram.setLineThickness(1);
		diagram.setForegroundColor(new RGB(255,255,255));//white

		if( name != null) {
			diagram.setName(name);
		}
		//Darpan
		ModelManagerListener.firePropertyChange(DIAGRAM_ABOUT_TO_ADD, null, null);
		// Add it to resource, so that it will be saved.
		getDiResource().getContents().add(diagram);
		// fire change 
		ModelManagerListener.firePropertyChange(CHILD_ADDED_PROP, null, diagram);

		return diagram;
	}

	/**
	 * 
	 * 
	 * @param diagram 
	 */
	public boolean removeDiagram(Diagram diagram) {
		if(diagram == null) {
			return false;
		}	
		// remove it from resources
		boolean result=getDiResource().getContents().remove(diagram);
		// Uml1SemanticModelBridge semantic= Di2Factory.eINSTANCE.createUml1SemanticModelBridge();

		// Detach diagram from UML model 
		// (should cause ElementTreeEditPart refresh for owner)
		diagram.setOwner(null);

		// fire change -- Remove diagram tab in editor
		ModelManagerListener.firePropertyChange(CHILD_REMOVED_PROP, null, diagram);
		return result;
	}

	/**
	 * 
	 * 
	 * @return the diagramListener
	 */
	public PropertyChangeSupport getDiagramListener() {
		return ModelManagerListener;
	}

	/**
	 * 
	 * 
	 * @param elt 
	 * 
	 * @return 
	 */
	public Collection<Diagram> lookForOwnedDiagrams(Element elt) {
		Collection<Diagram> diagrams = getDiagrams();



		return diagrams;
	}

//	/**
//	 * Return an iterator on all the diagrams in the model.
//	 * 
//	 * @return 
//	 */
//	public Collection<Diagram> getDiagrams()
//	{
//		if (new FilteredCollectionViewFromIterator(diAllContentsProvider, diagramFilter).size() == 0) {
//
//			// No diagram, create default diagram
//			createDiagram(getTopPackageFromUMLResource(), "DefaultDiagram", TypeDiagram.CLASS_DIAGRAM);
//			getModelTreeEditPart().refresh();
//		}
//
//		return new FilteredCollectionViewFromIterator<Diagram>(diAllContentsProvider, diagramFilter); 
//	}


	/**
	 * Returns the collection of visible diagrams for this model
	 * @return the collection of visible diagrams for this model
	 */
	public List<Diagram> getVisibleDiagrams() {
		List<Diagram> visibleDiagrams = new ArrayList<Diagram>();

		Iterator<Diagram> it = getDiagrams().iterator();
		while(it.hasNext()) {
			Diagram diagram = it.next();
			if(diagram.isIsVisible()) {
				visibleDiagrams.add(diagram);
			}
		}
		
		if(visibleDiagrams.isEmpty()) {
			if(getDiagrams().size()>0) {
				visibleDiagrams.add(getDiagrams().get(0));
			}
		}
		return visibleDiagrams;
	}

	/**
	 * Returns all diagrams in the model
	 * @return all diagrams in the model
	 */
	public List<Diagram> getDiagrams() {
		final List<Diagram> diagrams = new ArrayList<Diagram>();

		Iterator<EObject> it = getDiResource().getAllContents();
		while(it.hasNext()) {
			new Di2Switch<Diagram>() {
				/* (non-Javadoc)
				 * @see com.cea.papyrus.diagraminterchange2.di2.util.Di2Switch#caseDiagram(com.cea.papyrus.diagraminterchange2.di2.Diagram)
				 */
				@Override
				public Diagram caseDiagram(Diagram object) {
					diagrams.add(object);
					return object;
				}
			}.doSwitch(it.next());
		}
		
		return diagrams;
	}


//	/**
//	* Return an iterator on all the diagrams in the model.
//	* @return 
//	*/
//	public Collection<Diagram> getVisibleDiagrams() {
//	// Init visible diagrams
//	Collection<Diagram> visibleDiagrams = new ArrayList<Diagram>();

//	visibleDiagrams.addAll( new FilteredCollectionViewFromIterator(diAllContentsProvider, visibleDiagramFilter));

//	// If no visible diagram
//	if (visibleDiagrams.size() == 0) {

//	// Get every diagram (return at least a default diagram)
//	Diagram firstDiag = getDiagrams().iterator().next();
//	// Add the first diagram to the list
//	visibleDiagrams.add(firstDiag);
//	}

//	return visibleDiagrams; 
//	}

	/**
	 * Inner class allowing access to the contents of the DI resource.
	 * 
	 * @author dumoulin
	 */
	private class DiAllContentsProvider implements FilteredCollectionViewFromIterator.IteratorProvider
	{

		/**
		 * 
		 * 
		 * @return 
		 */
		public Iterator iterator() {
			return getDiResource().getAllContents();
		}

	}

	/**
	 * inner class.
	 * 
	 * @author dumoulin
	 */
	private static class DiagramFilter implements IFilter {

		/**
		 * 
		 * 
		 * @param object 
		 * 
		 * @return 
		 */
		public boolean isAllowed(Object object) {
			return object instanceof Diagram;
		}
	}

	/**
	 * inner class.
	 * 
	 * @author dumoulin
	 */
	private static class VisibleDiagramFilter implements IFilter {

		/**
		 * 
		 * 
		 * @param object 
		 * 
		 * @return 
		 */
		public boolean isAllowed(Object object) {
			return (object instanceof Diagram) && ((Diagram)object).isIsVisible();
		}
	}

	/**
	 * 
	 * 
	 * @param newModelTreeEditPart 
	 */
	public void setModelTreeEditPart(ElementTreeEditPart newModelTreeEditPart) {

		// Current modelTreeEditPart should not be already affected
		if (modelTreeEditPart != null) {
			return;
		}
		// New model tree edit part should not be null
		if (newModelTreeEditPart == null) {
			return;
		}

		Element element = newModelTreeEditPart.getUmlElement();

		// model is Model or Profile
		if (! ((element instanceof Model) || (element instanceof Profile) || (element instanceof Package))) {
			return;
		}

		Package model = (Package) element;

		// Check if the package is really the root package
		if (getTopPackageFromUMLResource().equals(model)) {			
			modelTreeEditPart = newModelTreeEditPart;
		}
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	public ElementTreeEditPart getModelTreeEditPart() {

		return modelTreeEditPart;
	}

	/**
	 * Returns the DiResourceSet associated to this ModelManager
	 * @return the diResourceSet associated to this ModelManager
	 */ 
	public DiResourceSet getDiResourceSet() {
		return diResourceSet;
	}

	/**
	 * Returns the ResourceSet holding models used by this ModelManager
	 * @return the ResourceSet holding models used by this ModelManager
	 */ 
	public ResourceSet getResourceSet() {
		return diResourceSet.getResourceSet();
	}

	/**
	 * 
	 * @return the adapterfactory of papyrus ressources
	 */
	public AdapterFactory getAdapterFactory(){
		return diResourceSet.getAdapterFactory();
	}
	
	/**
	 * Returns the label provider for manipulated elements
	 * @return the lavel provider for manipulated elements
	 */
	public UMLDecoratedLabelProvider getLabelProvider() {
		return adapterFactoryLabelProvider;
	}

	/**
	 * Returns the comparator for the outline tree viewer
	 * @return the comparator for the outline tree viewer
	 */
	public Comparator getOutlineSorter() {
		return sorter;
	}
	
	/**
	 * Sets the comparator for the outline tree viewer
	 * @param comparator the new sorter for the outline
	 */
	public void setOutlineSorter(Comparator comparator) {
		sorter = comparator;
	}
}
